﻿#region Using

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Vk.Methods.Common;
using Vk.Methods.Users;

#endregion

namespace Vk.Methods.Friends
{
    /// <summary>
    ///     Друзья
    /// </summary>
    public class Friends
    {
        private readonly Api _api;

        public Friends(Api api)
        {
            _api = api;
        }

        /// <summary>
        ///     Если идентификатор выбранного пользователя присутствует в списке заявок на добавление в друзья, полученным методом
        ///     friends.getRequests, то одобряет заявку на добавление и добавляет выбранного пользователя в друзья к текущему
        ///     пользователю. В противном случае создает заявку на добавление в друзья текущего пользователя к выбранному
        ///     пользователю.
        /// </summary>
        /// <param name="userId">
        ///     Идентификатор пользователя которому необходимо отправить заявку, либо заявку от которого
        ///     необходимо одобрить.
        /// </param>
        /// <param name="text">
        ///     Текст сопроводительного сообщения для заявки на добавление в друзья. Максимальная длина сообщения -
        ///     500 символов.
        /// </param>
        /// <param name="follow">true, если необходимо отклонить входящую заявку (оставить пользователя в подписчиках). </param>
        /// <returns>
        ///     В случае успешной отправки заявки на добавление в друзья возвращает 1. В случае успешного одобрения заявки на
        ///     добавление в друзья в возвращает 2. В случае повторной отправки заявки возвращает 4.
        /// </returns>
        public async Task<int> Add(int userId, string text = null, bool? follow = null)
        {
            var query = new ApiQuery("friends.add")
                .Add("user_id", userId)
                .Add("text", text)
                .Add("follow", follow);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Int().HasValue ? result.Int().Value : -1;
        }

        /// <summary>
        ///     Создает новый список друзей у текущего пользователя.
        /// </summary>
        /// <param name="name">Название создаваемого списка друзей.</param>
        /// <param name="userIds">
        ///     Перечисленные через запятую идентификаторы друзей пользователя, которых необходимо включить в
        ///     создаваемый список. Идентификаторы пользователей, не являющихся друзьями текущего пользователя, игнорируются.
        /// </param>
        /// <returns>В случае успеха возвращает идентификатор (listId) созданного списка друзей. </returns>
        public async Task<int> AddList(string name, IEnumerable<int> userIds = null)
        {
            if (name == null)
            {
                throw new ArgumentNullException("name");
            }

            var query = new ApiQuery("friends.addList")
                .Add("name", name)
                .Add("user_ids", userIds);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Int().HasValue ? result.Int().Value : -1;
        }

        /// <summary>
        ///     Возвращает информацию о том добавлен ли текущий пользователь в друзья у указанных пользователей.
        ///     Также возвращает информацию о наличии исходящей или входящей заявки в друзья (подписки).
        /// </summary>
        /// <param name="userIds">
        ///     Список идентификаторов пользователей, раделённых запятыми, статус дружбы с которыми необходимо
        ///     получить.
        /// </param>
        /// <param name="needSign"></param>
        public async Task<List<FriendStatus>> AreFriends(IEnumerable<int> userIds, bool? needSign = null)
        {
            var query = new ApiQuery("friends.areFriends")
                .Add("user_ids", userIds)
                .Add("need_sign", needSign);

            var result = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                return !result.ChildNodes.Any()
                           ? null
                           : result.SelectNodes("//status").Select(y => new FriendStatus(y)).ToList();
            }
            return null;
        }

        /// <summary>
        ///     Если идентификатор выбранного пользователя присутствует в списке заявок на добавление в друзья, полученным методом
        ///     friends.getRequests, то отклоняет заявку на добавление в друзья к текущему пользователю. В противном случае удаляет
        ///     выбранного пользователя из списка друзей текущего пользователя, который может быть получен методом friends.get.
        /// </summary>
        /// <param name="userId">
        ///     Идентификатор пользователя, которого необходимо удалить из списка друзей, либо заявку от которого
        ///     необходимо отклонить.
        /// </param>
        /// <returns>
        ///     В случае успешного удаления пользователя из списка друзей возвращает 1. В случае успешного отклонения заявки
        ///     на добавление в друзья возвращает 2. В случае успешного удаления рекомендации в друзья возвращает 3.
        /// </returns>
        public async Task<int> Delete(int userId)
        {
            var query = new ApiQuery("friends.delete")
                .Add("user_id", userId);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Int().HasValue ? result.Int().Value : -1;
        }

        /// <summary>
        ///     Отмечает все входящие заявки на добавление в друзья как просмотренные.
        /// </summary>
        /// <returns>Возвращает true в случае, если все заявки на добавление в друзья были успешно отклонены, иначе false. </returns>
        public async Task<bool> DeleteAllRequests()
        {
            var query = new ApiQuery("friends.deleteAllRequests");

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Удаляет существующий список друзей текущего пользователя.
        /// </summary>
        /// <param name="listId">Идентификатор списка друзей, который необходимо удалить. </param>
        /// <returns>После успешного выполнения возвращает true.</returns>
        public async Task<bool> DeleteList(int listId)
        {
            var query = new ApiQuery("friends.deleteList")
                .Add("list_id", listId);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Редактирует списки друзей для выбранного друга.
        /// </summary>
        /// <param name="userId">
        ///     Идентификатор пользователя (из числа друзей), для которого необходимо отредактировать списки
        ///     друзей.
        /// </param>
        /// <param name="listIds">Идентификаторы списков друзей, в которые нужно добавить пользователя. </param>
        /// <returns>После успешного выполнения возвращает true.</returns>
        public async Task<bool> Edit(int userId, IEnumerable<int> listIds = null)
        {
            var query = new ApiQuery("friends.edit")
                .Add("user_id", userId)
                .Add("list_ids", listIds);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Редактирует существующий список друзей текущего пользователя.
        /// </summary>
        /// <param name="listId">Идентификатор существующего списка друзей.</param>
        /// <param name="name">Название списка друзей.</param>
        /// <param name="userIds">
        ///     Перечисленные через запятую идентификаторы друзей пользователя, которым необходимо поставить метку.
        ///     Идентификаторы пользователей, не являющихся друзьями текущего пользователя, игнорируются.
        /// </param>
        /// <param name="addUserIds">
        ///     Идентификаторы пользователей, которых необходимо добавить в список. (в случае если не передан
        ///     user_ids)
        /// </param>
        /// <param name="deleteUserIds">
        ///     Идентификаторы пользователей, которых необходимо изъять из списка. (в случае если не
        ///     передан user_ids)
        /// </param>
        /// <returns>В случае успеха возвращает true. </returns>
        public async Task<bool> EditList(int listId,
                                         string name,
                                         IEnumerable<int> userIds = null,
                                         IEnumerable<int> addUserIds = null,
                                         IEnumerable<int> deleteUserIds = null)
        {
            var query = new ApiQuery("friends.editList")
                .Add("list_id", listId)
                .Add("name", name)
                .Add("user_ids", userIds)
                .Add("add_user_ids", addUserIds)
                .Add("delete_user_ids", deleteUserIds);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Возвращает список идентификаторов друзей пользователя или расширенную информацию о друзьях пользователя (при
        ///     использовании параметра fields).
        /// </summary>
        /// <param name="userId">
        ///     Идентификатор пользователя, для которого необходимо получить список друзей. Если параметр не
        ///     задан, то считается, что он равен идентификатору текущего пользователя.
        /// </param>
        /// <param name="nameCase">
        ///     Падеж для склонения имени и фамилии пользователя. Возможные значения: именительный – nom,
        ///     родительный – gen, дательный – dat, винительный – acc, творительный – ins, предложный – abl. По умолчанию nom.
        /// </param>
        /// <param name="count">Количество друзей, которое нужно вернуть. (по умолчанию – все друзья)</param>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества друзей.</param>
        /// <param name="listId">
        ///     Идентификатор списка друзей, полученный методом friends.getLists, друзей из которого необходимо
        ///     получить. Данный параметр учитывается, только когда параметр userId равен идентификатору текущего пользователя.
        /// </param>
        /// <param name="fields">
        ///     Перечисленные через запятую поля анкет, необходимые для получения. Доступные значения: userId,
        ///     first_name, last_name, nickname, sex, bdate (birthdate), city, country, timezone, photo, photo_medium, photo_big,
        ///     domain, has_mobile, rate, contacts, education.
        /// </param>
        /// <param name="order">
        ///     Порядок в котором нужно вернуть список друзей. Допустимые значения: name - сортировать по имени
        ///     (работает только при переданном параметре fields). hints - сортировать по рейтингу, аналогично тому, как друзья
        ///     сортируются в разделе Мои друзья
        /// </param>
        public async Task<ListCount<User>> Get(int? userId = null,
                                               IEnumerable<string> fields = null,
                                               string nameCase = null,
                                               int? listId = null,
                                               int? offset = null,
                                               int? count = null,
                                               FriendsResultOrder order = null)
        {
            var query = new ApiQuery("friends.get")
                .Add("user_id", userId)
                .Add("name_case", nameCase)
                .Add("count", count)
                .Add("offset", offset)
                .Add("list_id", listId)
                .Add("fields", fields)
                .Add("order", order.Value);

            var result = await _api.Manager.Execute(query);

            if (_api.Manager.MethodSuccessed)
            {
                if (!result.ChildNodes.Any())
                {
                    return null;
                }
                return fields != null && fields.Any()
                           ? BuildFriendsObjectsList(result)
                           : new ListCount<User>(result.Int("count").Value,
                                                 result.SelectNodes("items/*").Select(x => new User(x)).ToList());
            }
            return null;
        }

        /// <summary>
        ///     Возвращает список идентификаторов друзей текущего пользователя, которые установили данное приложение.
        /// </summary>
        public async Task<List<int>> GetAppUsers()
        {
            var query = new ApiQuery("friends.getAppUsers");

            var result = await _api.Manager.Execute(query);

            if (_api.Manager.MethodSuccessed)
            {
                return !result.ChildNodes.Any()
                           ? null
                           : result.ChildNodes.Select(x => x.Int().Value).ToList();
            }
            return null;
        }

        /// <summary>
        ///     Позволяет получить список идентификаторов пользователей, доступных для вызова в приложении, используя метод JSAPI
        ///     callUser.
        /// </summary>
        /// <param name="fields">Cписок дополнительных полей, которые необходимо вернуть. <see cref="UserFields" /> </param>
        /// <param name="nameCase">Падеж для склонения имени и фамилии пользователя. </param>
        /// <returns>Возвращает список объектов пользователей.</returns>
        public async Task<ListCount<User>> GetAvailableForCall(IEnumerable<string> fields = null,
                                                               UserNameCase nameCase = null)
        {
            var query = new ApiQuery("friends.getAvailableForCall")
                .Add("fields", fields)
                .Add("name_case", nameCase);

            var result = await _api.Manager.Execute(query);

            if (_api.Manager.MethodSuccessed)
            {
                if (!result.ChildNodes.Any())
                {
                    return null;
                }
                return fields != null && fields.Any()
                           ? BuildFriendsObjectsList(result)
                           : new ListCount<User>(result.Int("count").Value,
                                                 result.SelectNodes("items/*").Select(x => new User(x)).ToList());
            }
            return null;
        }

        /// <summary>
        ///     Возвращает список друзей пользователя, у которых завалидированные или указанные в профиле телефонные номера входят
        ///     в заданный список.
        /// </summary>
        /// <remarks>
        ///     Использование данного метода возможно только если у текущего пользователя завалидирован номер мобильного
        ///     телефона. Для проверки этого условия можно использовать метод users.get c параметрами uids=API_USER и
        ///     fields=has_mobile, где API_USER равен идентификатору текущего пользователя. Для доступа к этому методу приложение
        ///     должно быть доверенным.
        /// </remarks>
        /// <param name="phones">Список телефонных номеров в формате MSISDN. Максимальное количество номеров в списке — 1000. </param>
        /// <param name="fields">Список дополнительных полей, которые необходимо вернуть. </param>
        /// <seealso cref="UserFields" />
        /// <returns>
        ///     После успешного выполнения возвращает словарь, ключем которого есть номер телефона, а значением - обьект
        ///     пользователя
        /// </returns>
        public async Task<Dictionary<string, User>> GetByPhones(IEnumerable<string> phones,
                                                                IEnumerable<string> fields = null)
        {
            if (phones == null || !phones.Any())
                throw new ArgumentNullException(nameof(phones));

            var query = new ApiQuery("friends.getByPhones")
                .Add("phones", phones)
                .Add("fields", fields);

            var result = await _api.Manager.Execute(query);

            if (_api.Manager.MethodSuccessed)
            {
                var dictionary = new Dictionary<string, User>();
                foreach (var node in result.ChildNodes)
                {
                    dictionary.Add(node.String("phone"), new User(node));
                }
                return dictionary;
            }
            return null;
        }

        /// <summary>
        ///     Возвращает список меток друзей текущего пользователя.
        /// </summary>
        /// <param name="userId">Идентификатор пользователя. </param>
        /// <param name="returnSystem">Возвращать ли системный список публичных меток друзей пользователя. </param>
        /// <returns>Возвращает массив меток друзей текущего пользователя, каждый из объектов которого содержит поля id и name. </returns>
        public async Task<ListCount<IdTitleObject>> GetLists(int? userId = null, bool? returnSystem = null)
        {
            var query = new ApiQuery("friends.getLists")
                .Add("user_id", userId)
                .Add("return_system", returnSystem);

            var result = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                return new ListCount<IdTitleObject>(result.Int("count").Value,
                                                    result.SelectNodes("items/*")
                                                          .Select(y => new IdTitleObject(y, titleNodeName: "name"))
                                                          .ToList());
            }
            return null;
        }

        /// <summary>
        ///     Возвращает список идентификаторов общих друзей между парой пользователей.
        /// </summary>
        /// <param name="targetUid">Идентификатор пользователя, с которым необходимо искать общих друзей.</param>
        /// <param name="sourceUid">
        ///     Идентификатор пользователя, чьи друзья пересекаются с друзьями пользователя с идентификатором
        ///     target_userId. Если параметр не задан, то считается, что source_userId равен идентификатору текущего пользователя.
        /// </param>
        /// <param name="targetUids">Список идентификаторов пользователей, с которыми необходимо искать общих друзей. </param>
        /// <param name="random">true - возвращает друзей в случайном порядке</param>
        /// <param name="count">Количество общих друзей, которое нужно вернуть. (по умолчанию – все общие друзья) </param>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества общих друзей. </param>
        public async Task<List<int>> GetMutual(int? sourceUid = null,
                                               int? targetUid = null,
                                               IEnumerable<int> targetUids = null,
                                               bool random = false,
                                               int? count = null,
                                               int? offset = null)
        {
            if (targetUid == null && targetUids == null)
            {
                throw new ArgumentException("At least one of parameters must be not null: targetUid, targetUids");
            }

            var query = new ApiQuery("friends.getMutual")
                .Add("target_uid", targetUid)
                .Add("target_uids", targetUids)
                .Add("source_uid", sourceUid)
                .Add("order", "random")
                .Add("count", count)
                .Add("offset", offset);

            var result = await _api.Manager.Execute(query);

            if (_api.Manager.MethodSuccessed)
            {
                return !result.ChildNodes.Any()
                           ? null
                           : result.ChildNodes.Select(x => x.Int().Value).ToList();
            }
            return null;
        }

        /// <summary>
        ///     Возвращает список идентификаторов друзей пользователя, находящихся на сайте.
        /// </summary>
        /// <param name="userId">
        ///     Идентификатор пользователя, для которого необходимо получить список друзей онлайн. Если параметр
        ///     не задан, то считается, что он равен идентификатору текущего пользователя.
        /// </param>
        /// <param name="listId">
        ///     Идентификатор списка друзей. Если параметр не задан, возвращается информация обо всех друзьях,
        ///     находящихся на сайте.
        /// </param>
        /// <param name="onlineMobile">true — будет возвращено дополнительное поле online_mobile. </param>
        /// <param name="order">
        ///     Порядок, в котором нужно вернуть список друзей, находящихся на сайте. Допустимые значения: random -
        ///     возвращает друзей в случайном порядке, hints - сортировать по рейтингу, аналогично тому, как друзья сортируются в
        ///     разделе Мои друзья (данный параметр доступен только для Desktop-приложений).
        /// </param>
        /// <param name="count">Количество друзей онлайн, которое нужно вернуть. (по умолчанию – все друзья онлайн) </param>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества друзей онлайн. </param>
        public async Task<GetOnlineResult> GetOnline(int? userId = null,
                                                     int? listId = null,
                                                     bool? onlineMobile = null,
                                                     RandomUsersResultOrder order = null,
                                                     int? count = null,
                                                     int? offset = null)
        {
            var query = new ApiQuery("friends.getOnline")
                .Add("user_id", userId)
                .Add("count", count)
                .Add("offset", offset)
                .Add("list_id", listId)
                .Add("online_mobile", onlineMobile)
                .Add("order", order.Value);

            var result = await _api.Manager.Execute(query);

            if (_api.Manager.MethodSuccessed)
            {
                var res = new GetOnlineResult();
                if (!result.ChildNodes.Any())
                {
                    return null;
                }

                //Когда onlineMobile равен false или null, в корне ответа возвращается список пользователей, которые онлайн с десктопов
                var onlineNodes = result.SelectNodes(onlineMobile.HasValue && onlineMobile.Value ? "online/*" : "*");
                if (onlineNodes.Any())
                    res.Online = onlineNodes.Select(y => y.Int()).Where(x => x.HasValue).Select(x => x.Value).ToList();

                if (onlineMobile.HasValue && onlineMobile.Value)
                {
                    var onlineMobileNodes = result.SelectNodes("online_mobile/*");
                    if (onlineMobileNodes.Any())
                        res.OnlineMobile =
                            onlineMobileNodes.Select(y => y.Int()).Where(x => x.HasValue).Select(x => x.Value).ToList();
                }

                return res;
            }
            return null;
        }

        /// <summary>
        ///     Возвращает список идентификаторов недавно добавленных друзей текущего пользователя
        /// </summary>
        /// <param name="count">
        ///     Максимальное количество недавно добавленных друзей, которое необходимо получить. (100 по умолчанию,
        ///     макс. 1000)
        /// </param>
        /// <returns>
        ///     После успешного выполнения возвращает отсортированный в антихронологическом порядке список идентификаторов
        ///     (id) недавно добавленных друзей текущего пользователя.
        /// </returns>
        public async Task<List<int>> GetRecent(int? count = null)
        {
            var query = new ApiQuery("friends.getRecent")
                .Add("count", count);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed
                       ? result.ChildNodes.Select(x => x.Int().Value).ToList()
                       : null;
        }

        /// <summary>
        ///     Возвращает информацию о полученных или отправленных заявках на добавление в друзья для текущего пользователя.
        /// </summary>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества заявок на добавление в друзья.</param>
        /// <param name="count">
        ///     Максимальное количество заявок на добавление в друзья, которые необходимо получить (не более 1000).
        ///     Если параметр не задан, то считается, что он равен 100.
        /// </param>
        /// <param name="extended">
        ///     Определяет требуется ли возвращать в ответе сообщения от пользователей, подавших заявку на
        ///     добавление в друзья. И отправителя рекомендации при suggested=1.
        /// </param>
        /// <param name="needMutual">
        ///     Определяет требуется ли возвращать в ответе список общих друзей, если они есть. Обратите
        ///     внимание, что при использовании need_mutual будет возвращено не более 20 заявок.
        /// </param>
        /// <param name="out">
        ///     false - возвращать полученные заявки в друзья (по умолчанию), true - возвращать отправленные
        ///     пользователем заявки.
        /// </param>
        /// <param name="suggested">
        ///     true - возвращать рекомендованных другими пользователями друзей, false - возвращать заявки в
        ///     друзья (по умолчанию).
        /// </param>
        /// <param name="sort">
        ///     0 - сортировать по дате добавления, 1 - сортировать по количеству общих друзей. (Если out = true –
        ///     данный параметр работать не будет.)
        /// </param>
        public async Task<ListCount<RequestInfo>> GetRequests(int? offset = null,
                                                              int? count = null,
                                                              bool? extended = null,
                                                              bool? needMutual = null,
                                                              bool? @out = null,
                                                              bool? suggested = null,
                                                              FriendsSortOrder sort = null,
                                                              bool? needViewed = null)
        {
            var query = new ApiQuery("friends.getRequests")
                .Add("offset", offset)
                .Add("count", count)
                .Add("extended", extended)
                .Add("need_mutual", needMutual)
                .Add("need_viewed", needViewed)
                .Add("out", @out)
                .Add("suggested", suggested)
                .Add("sort", sort.Value);

            var result = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                return !result.ChildNodes.Any()
                           ? null
                           : new ListCount<RequestInfo>(result.Int("count").Value,
                                                        result.SelectNodes("//items/*")
                                                              .Select(y => new RequestInfo(y))
                                                              .ToList());
            }
            return null;
        }

        /// <summary>
        ///     Возвращает список профилей пользователей, которые могут быть друзьями текущего пользователя.
        ///     Для того, чтобы данный метод вернул достаточное количество подсказок необходимо предварительно вызвать метод
        ///     account.importContacts.
        /// </summary>
        /// <param name="filter">
        ///     Типы предлагаемых друзей которые нужно вернуть, перечисленные через запятую.
        ///     Параметр может принимать следующие значения:
        ///     mutual - пользователи, с которыми много общих друзей,
        ///     contacts - пользователи найденные благодаря методу account.importContacts.
        ///     mutual_contacts - пользователи, которые импортировали те же контакты что и текущий пользователь, используя метод
        ///     account.importContacts.
        ///     По умолчанию будут возвращены все возможные друзья.
        /// </param>
        /// <param name="offset">Cмещение необходимое для выбора определённого подмножества списка.</param>
        /// <param name="count">Количество рекомендаций, которое необходимо вернуть.</param>
        /// <returns>
        ///     В результате выполнения данного метода, будет возвращён список профилей, которые могут быть предложены
        ///     пользователю в качестве возможных друзей.
        /// </returns>
        public async Task<ListCount<User>> GetSuggestions(IEnumerable<string> filter = null,
                                                          int? offset = null,
                                                          int? count = null,
                                                          string nameCase = null,
                                                          IEnumerable<string> fields = null)
        {
            var query = new ApiQuery("friends.getSuggestions")
                .Add("filter", filter)
                .Add("fields", fields)
                .Add("offset", offset)
                .Add("count", count)
                .Add("name_case", nameCase);

            var result = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                return new ListCount<User>(result.Int("count").Value, !result.ChildNodes.Any()
                                                                          ? null
                                                                          : result.SelectNodes("items/*")
                                                                                  .Select(y => new User(y))
                                                                                  .ToList());
            }
            return null;
        }

        /// <summary>
        ///     Позволяет искать по списку друзей пользователей.
        ///     Для расширенного поиска по списку друзей можно использовать метод users.search с параметром from_list = friends.
        /// </summary>
        /// <param name="userId">Идентификатор пользователя, по списку друзей которого необходимо произвести поиск. </param>
        /// <param name="q">Строка запроса. </param>
        /// <param name="fields">Список дополнительных полей, которые необходимо вернуть. </param>
        /// <param name="nameCase">Падеж для склонения имени и фамилии пользователя. </param>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества друзей. </param>
        /// <param name="count">Количество друзей, которое нужно вернуть. (по умолчанию 20, макс. 1000)</param>
        /// <returns></returns>
        public async Task<ListCount<User>> Search(int? userId = null,
                                                  string q = null,
                                                  IEnumerable<string> fields = null,
                                                  UserNameCase nameCase = null,
                                                  int? offset = null,
                                                  int? count = null)
        {
            var query = new ApiQuery("friends.getSuggestions")
                .Add("user_id", userId)
                .Add("q", q)
                .Add("fields", fields)
                .Add("offset", offset)
                .Add("count", count)
                .Add("name_case", nameCase);

            var result = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                return new ListCount<User>(result.Int("count").Value, !result.ChildNodes.Any()
                                                                          ? null
                                                                          : result.SelectNodes("items/*")
                                                                                  .Select(y => new User(y))
                                                                                  .ToList());
            }
            return null;
        }

        /// <summary>
        ///     Возвращает список сущностей друзей
        /// </summary>
        /// <param name="node">Xml-элемент, который содержит список</param>
        internal static ListCount<User> BuildFriendsObjectsList(ApiNode x)
        {
            var msgsNodes = x.SelectNodes("//user");
            return msgsNodes.Any()
                       ? new ListCount<User>(x.Int("count").Value, msgsNodes.Select(y => new User(y)).ToList())
                       : null;
        }
    }

    /// <summary>
    ///     Результат метода GetOnline
    /// </summary>
    public class GetOnlineResult
    {
        /// <summary>
        ///     Список идентификаторов пользователей, которые онлайн
        /// </summary>
        public List<int> Online { get; set; }

        /// <summary>
        ///     Список идентификаторов пользователей, которые онлайн с мобильного устройства
        /// </summary>
        public List<int> OnlineMobile { get; set; }
    }
}